﻿/*
* Copyright (c) 2012 Kyle Hayward
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

using SlimDX;
using SlimDX.Direct3D11;
using SlimDX.DXGI;
using SlimDX.Windows;
using SlimDX.D3DCompiler;

using Device = SlimDX.Direct3D11.Device;
using Buffer = SlimDX.Direct3D11.Buffer;

using BoundingBox = OptixDotNet.Math.BoundingBox;
using OptixMath = OptixDotNet.Math.MathUtils;

namespace InstantRadiosity
{
    public struct CustomLayout
    {
        public Format Position;
        public Format Normal;
        public Format Texcoord;
    }

    public struct ScreenVert
    {
        public Vector4 Position;
        public Vector2 Texcoord;

        public ScreenVert( float x, float y, float z, float u, float v )
        {
            Position = new Vector4( x, y, z, 1.0f );
            Texcoord = new Vector2( u, v );
        }
    }

    public struct PointLight
    {
        // Position.w = radius
        public Vector4 Position;
        public Vector4 Color;

        public static int SizeInBytes { get { return Vector4.SizeInBytes * 2; } }
    }

    public struct AreaLight
    {
        // Position.w = radius
        public Vector4 Position;
        public Vector4 Color;
        public Vector4 Corner;
        public Vector4 V1;
        public Vector4 V2;

        public static int SizeInBytes { get { return Vector4.SizeInBytes * 5; } }
    }

    public struct CameraData
    {
        public Matrix View;
        public Matrix Proj;
        public Matrix ViewProj;
    }

    public struct Shader
    {
        public InputLayout Layout;
        public VertexShader VertexShader;
        public PixelShader PixelShader;
    }

    public struct D3DMaterial
    {
        public Vector4 Diffuse;
        public Vector4 Emission;
    }

    public struct Packet
    {
        public int          VertexCount;
        public int          VertexOffset;
        public int          VertexStride;

        public PrimitiveTopology Topology;
        public int          IndexCount;
        public int          IndexOffset;

        public Buffer       VertexBuffer;
        public Buffer       IndexBuffer;

        public Shader       Shader;

        public D3DMaterial  Material;
        public Buffer       MtrlBuffer;
    }

    public struct ShadowTarget
    {
        public CameraData Camera;
        public Buffer CameraBuffer;

        public Texture2D Texture;
        public RenderTargetView TextureRTV;
        public ShaderResourceView TextureSRV;

        public Texture2D DepthBuffer;
        public DepthStencilView DepthBufferView;

        public Shader Shader;
        public SamplerState Sampler;

        public ShadowTarget( Device device, int width, int height, Format format, Format depthFormat, Vector3 pos, Vector3 target, Vector3 up )
        {
            Texture = new Texture2D( device, new Texture2DDescription()
            {
                ArraySize = 1,
                BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource,
                Usage = ResourceUsage.Default,
                Format = format,
                Width = width,
                Height = height,
                MipLevels = 1,
                SampleDescription = new SampleDescription( 1, 0 )
            } );

            TextureSRV = new ShaderResourceView( device, Texture );
            TextureRTV = new RenderTargetView( device, Texture );

            Texture2DDescription depthBufferDesc = new Texture2DDescription()
            {
                ArraySize = 1,
                BindFlags = BindFlags.DepthStencil,
                Usage = ResourceUsage.Default,
                Format = depthFormat,
                Width = width,
                Height = height,
                MipLevels = 1,
                SampleDescription = new SampleDescription( 1, 0 )
            };

            DepthBuffer = new Texture2D( device, depthBufferDesc );
            DepthBufferView = new DepthStencilView( device, DepthBuffer );

            Camera = new CameraData();
            Camera.Proj = Matrix.PerspectiveFovRH( 90.0f * OptixMath.DegreesToRadians, (float)width / (float)height, 2.0f, 50.0f );

            Camera.View = Matrix.LookAtRH( pos, target, up );
            Camera.ViewProj = Camera.View * Camera.Proj;

            DataStream stream = new DataStream( Marshal.SizeOf( typeof( CameraData ) ), true, true );
            stream.Write( Camera );
            stream.Position = 0;

            CameraBuffer = new Buffer( device, stream, new BufferDescription()
            {
                BindFlags = BindFlags.ConstantBuffer,
                CpuAccessFlags = CpuAccessFlags.None,
                OptionFlags = ResourceOptionFlags.None,
                Usage = ResourceUsage.Default,
                SizeInBytes = Marshal.SizeOf( typeof( CameraData ) )
            } );

            Shader = default( Shader );

            Sampler = SamplerState.FromDescription( device, new SamplerDescription()
            {
                Filter = Filter.MinMagMipLinear,
                AddressU = TextureAddressMode.Clamp,
                AddressV = TextureAddressMode.Clamp,
                AddressW = TextureAddressMode.Clamp,
                MipLodBias = 0.0f,
                MaximumAnisotropy = 1,
                ComparisonFunction = Comparison.Always,
                BorderColor = new Color4( 1, 1, 1, 0 ),
                MinimumLod = 0,
                MaximumLod = float.MaxValue
            } );
        }
    }

    public class Renderer
    {
        #region Fields
        public Device Device;
        public SwapChain SwapChain;

        public Buffer VPLBuffer;

        public SlimDX.Color4 ClearColor;

        private CameraData mCamera;

        private int mWidth;
        private int mHeight;

        private Texture2D mBackBuffer;
        private RenderTargetView mBackBufferView;
        private RenderTargetView mLightView;

        private Texture2D mDepthBuffer;
        private DepthStencilView mDepthBufferView;

        private ShaderResourceView mLightSRV;

        private ShaderResourceView mVPLView;
        private ShaderResourceView mVoxelsView;
        private UnorderedAccessView mVPLVoxelsView;
        private ShaderResourceView mVPLVoxelsShaderView;

        private DepthStencilState mDepthState;
        private DepthStencilState mLightDepthState;
        private DepthStencilState mScreenDepthState;

        private RasterizerState mRasterState;
        private RasterizerState mScreenRasterState;

        private SamplerState mSampler2D;

        private BlendState mNoBlend;
        private BlendState mAddBlend;

        private List<Packet> mPackets;
        private List<AreaLight> mLights;

        private Buffer mCameraBuffer;
        private Buffer mLightBuffer;

        private Buffer mVoxelsBufffer;
        private Buffer mVPLVoxelsBuffer;

        private Shader mAmbientShader;
        private Shader mScreenShader;
        private ComputeShader mMergeShader;

        private CustomLayout mDefaultLayout;

        private ShadowTarget mShadow;

        private int mNumThreadGroups = 1; 
        #endregion

        public Renderer( RenderForm form )
        {
            mWidth = form.ClientSize.Width;
            mHeight = form.ClientSize.Height;
            ClearColor = new SlimDX.Color4( 1.0f, .39f, .58f, .93f );

            mPackets = new List<Packet>();
            mLights = new List<AreaLight>();

            mDefaultLayout = new CustomLayout() { Position = Format.R32G32B32_Float, Normal = Format.R32G32B32_Float, Texcoord = Format.R32G32_Float };

            CreateD3D( form.Handle );
            CreateStates();
            CreateConstantBuffers();
            CreateScreenQuad();

            mAmbientShader = CreateShader( "../../Assets/Shaders/Ambient.hlsl", "VSMain", "PSMain" );

            VPLBuffer = new Buffer( Device, new BufferDescription()
            {
                BindFlags = BindFlags.ShaderResource,
                OptionFlags = ResourceOptionFlags.StructuredBuffer,
                CpuAccessFlags = CpuAccessFlags.None,
                Usage = ResourceUsage.Default,
                SizeInBytes = PointLight.SizeInBytes * (int)InstantRadiosity.TotalVPLs,
                StructureByteStride = PointLight.SizeInBytes
            } );

            mVPLView = new ShaderResourceView( Device, VPLBuffer, new ShaderResourceViewDescription()
            {
                ElementOffset = 0,
                ElementWidth = (int)InstantRadiosity.TotalVPLs,
                Format = Format.Unknown,
                Dimension = ShaderResourceViewDimension.Buffer
            } );
        }

        public void AddPacket( ref Packet packet )
        {
            mPackets.Add( packet );
        }

        public void AddLight( ref AreaLight light )
        {
            //only one light supported :(
            if ( mLights.Count > 0 )
                return;

            mLights.Add( light );

            Vector3 pos = new Vector3( light.Position.X, light.Position.Y, light.Position.Z );
            Vector3 target = pos + new Vector3( 0.0f, -5.0f, 0.0f );

            mShadow = new ShadowTarget( Device, 1024, 1024, Format.R32G32_Float, Format.D24_UNorm_S8_UInt, pos, target, Vector3.UnitZ );
            mShadow.Shader = CreateShader( "../../Assets/Shaders/Depth.hlsl", "VSMain", "PSMain" );
        }

        public Shader CreateShader( string path, string vertex, string pixel )
        {
            return CreateShader( path, vertex, pixel, mDefaultLayout, true );
        }

        public Shader CreateShader( string path, string vertex, string pixel, CustomLayout userlayout, bool createLayout )
        {
            ShaderFlags flags = ShaderFlags.Debug | ShaderFlags.PackMatrixRowMajor;

            Shader shader = new Shader();
            ShaderBytecode vertexCode = ShaderBytecode.CompileFromFile( path, vertex, "vs_5_0", flags, EffectFlags.None );
            ShaderBytecode pixelCode = ShaderBytecode.CompileFromFile( path, pixel, "ps_5_0", flags, EffectFlags.None );

            shader.VertexShader = new VertexShader( Device, vertexCode );
            shader.PixelShader = new PixelShader( Device, pixelCode );
            shader.Layout = null;

            ShaderReflection reflection = new ShaderReflection( vertexCode );

            if ( createLayout )
            {
                InputElement[] layout = new InputElement[ reflection.Description.InputParameters ];

                for ( int i = 0; i < layout.Length; i++ )
                {
                    ShaderParameterDescription desc = reflection.GetInputParameterDescription( i );

                    layout[ i ].SemanticName = desc.SemanticName;
                    layout[ i ].SemanticIndex = (int)desc.SemanticIndex;
                    layout[ i ].Slot = 0;
                    layout[ i ].Classification = InputClassification.PerVertexData;
                    layout[ i ].InstanceDataStepRate = 0;
                    layout[ i ].AlignedByteOffset = InputElement.AppendAligned;
                    layout[ i ].Format = GetFormatFromSemantic( desc.SemanticName, userlayout );
                }

                shader.Layout = new InputLayout( Device, vertexCode, layout );
            }

            return shader;
        }

        public void CreateVPLData( string computeShaderPath, string computeShaderMain, List<BoundingBox> voxels, int firstLeafIndex )
        {
            if ( voxels == null )
                return;

            mNumThreadGroups = voxels.Count / 16;

            //create initial voxel vpl data
            DataStream stream = new DataStream( BoundingBox.SizeInBytes * voxels.Count, true, true );
            for ( int i = 0; i < voxels.Count; i++ )
            {
                stream.Write<OptixDotNet.Math.Vector3>( voxels[ i ].Min );
                stream.Write<OptixDotNet.Math.Vector3>( voxels[ i ].Max );
            }
            stream.Position = 0;

            mVoxelsBufffer = new Buffer( Device, stream, new BufferDescription()
            {
                BindFlags = BindFlags.ShaderResource,
                OptionFlags = ResourceOptionFlags.StructuredBuffer,
                CpuAccessFlags = CpuAccessFlags.None,
                Usage = ResourceUsage.Default,
                SizeInBytes = BoundingBox.SizeInBytes * voxels.Count,
                StructureByteStride = BoundingBox.SizeInBytes
            } );
            stream.Dispose();

            stream = new DataStream( PointLight.SizeInBytes * voxels.Count, true, true );
            for ( int i = 0; i < voxels.Count; i++ )
            {
                stream.Write( new Vector4( voxels[ i ].Center.X, voxels[ i ].Center.Y, voxels[ i ].Center.Z, 1.0f ) );
                stream.Write( Vector4.Zero );
            }
            stream.Position = 0;

            mVPLVoxelsBuffer = new Buffer( Device, stream, new BufferDescription()
            {
                BindFlags = BindFlags.ShaderResource | BindFlags.UnorderedAccess,
                OptionFlags = ResourceOptionFlags.StructuredBuffer,
                CpuAccessFlags = CpuAccessFlags.None,
                Usage = ResourceUsage.Default,
                SizeInBytes = PointLight.SizeInBytes * voxels.Count,
                StructureByteStride = PointLight.SizeInBytes
            } );

            mVoxelsView = new ShaderResourceView( Device, mVoxelsBufffer, new ShaderResourceViewDescription()
            {
                ElementOffset = 0,
                ElementWidth = voxels.Count,
                Format = Format.Unknown,
                Dimension = ShaderResourceViewDimension.Buffer
            } );

            mVPLVoxelsView = new UnorderedAccessView( Device, mVPLVoxelsBuffer, new UnorderedAccessViewDescription()
            {
                ElementCount = voxels.Count,
                Format = Format.Unknown,
                Dimension = UnorderedAccessViewDimension.Buffer
            } );

            mVPLVoxelsShaderView = new ShaderResourceView( Device, mVPLVoxelsBuffer, new ShaderResourceViewDescription()
            {
                ElementOffset = 0,
                ElementWidth = voxels.Count,
                Format = Format.Unknown,
                Dimension = ShaderResourceViewDimension.Buffer
            } );

            ShaderFlags flags = ShaderFlags.Debug | ShaderFlags.PackMatrixRowMajor;
            ShaderBytecode csCode = ShaderBytecode.CompileFromFile( computeShaderPath, computeShaderMain, "cs_5_0", flags, EffectFlags.None );
            mMergeShader = new ComputeShader( Device, csCode );
        }

        public void SetCamera( CameraData camera )
        {
            mCamera = camera;

            DataBox data = Device.ImmediateContext.MapSubresource( mCameraBuffer, MapMode.WriteDiscard, SlimDX.Direct3D11.MapFlags.None );
            data.Data.Write<CameraData>( camera );
            Device.ImmediateContext.UnmapSubresource( mCameraBuffer, 0 );
        }

        public void Render()
        {
            RunMerge();

            ShadowPass();

            BeginRender();

            //ambient pass
            Device.ImmediateContext.VertexShader.Set( mAmbientShader.VertexShader );
            Device.ImmediateContext.PixelShader.Set( mAmbientShader.PixelShader );

            Device.ImmediateContext.VertexShader.SetConstantBuffer( mCameraBuffer, 0 );
            for ( int i = 0; i < mPackets.Count; i++ )
            {
                Packet packet = mPackets[ i ];

                Device.ImmediateContext.PixelShader.SetConstantBuffer( packet.MtrlBuffer, 0 );

                Device.ImmediateContext.InputAssembler.InputLayout = mAmbientShader.Layout;
                Device.ImmediateContext.InputAssembler.PrimitiveTopology = packet.Topology;

                Device.ImmediateContext.InputAssembler.SetVertexBuffers( 0, new VertexBufferBinding( packet.VertexBuffer, packet.VertexStride, 0 ) );
                Device.ImmediateContext.InputAssembler.SetIndexBuffer( packet.IndexBuffer, Format.R32_UInt, 0 );

                Device.ImmediateContext.DrawIndexed( packet.IndexCount, 0, packet.VertexOffset );
            }

            //light pass
            Device.ImmediateContext.OutputMerger.BlendState = mAddBlend;
            Device.ImmediateContext.OutputMerger.DepthStencilState = mLightDepthState;

            ShaderResourceView[] pixelSRVs = { mVPLView, mVPLVoxelsShaderView, mShadow.TextureSRV };

            Device.ImmediateContext.PixelShader.SetShaderResources( pixelSRVs, 0, pixelSRVs.Length );
            Device.ImmediateContext.PixelShader.SetSampler( mShadow.Sampler, 0 );
            Device.ImmediateContext.PixelShader.SetConstantBuffer( mShadow.CameraBuffer, 2 );

            for ( int i = 0; i < mPackets.Count; i++ )
            {
                Packet packet = mPackets[ i ];

                if ( Device.ImmediateContext.VertexShader.Get() != packet.Shader.VertexShader )
                    Device.ImmediateContext.VertexShader.Set( packet.Shader.VertexShader );

                if ( Device.ImmediateContext.PixelShader.Get() != packet.Shader.PixelShader )
                    Device.ImmediateContext.PixelShader.Set( packet.Shader.PixelShader );

                Device.ImmediateContext.PixelShader.SetConstantBuffer( packet.MtrlBuffer, 0 );

                Device.ImmediateContext.InputAssembler.InputLayout = packet.Shader.Layout;
                Device.ImmediateContext.InputAssembler.PrimitiveTopology = packet.Topology;

                Device.ImmediateContext.InputAssembler.SetVertexBuffers( 0, new VertexBufferBinding( packet.VertexBuffer, packet.VertexStride, 0 ) );
                Device.ImmediateContext.InputAssembler.SetIndexBuffer( packet.IndexBuffer, Format.R32_UInt, 0 );

                for ( int light = 0; light < mLights.Count; light++ )
                {
                    DataBox data = Device.ImmediateContext.MapSubresource( mLightBuffer, MapMode.WriteDiscard, SlimDX.Direct3D11.MapFlags.None );
                    data.Data.Write<AreaLight>( mLights[ light ] );
                    Device.ImmediateContext.UnmapSubresource( mLightBuffer, 0 );

                    Device.ImmediateContext.PixelShader.SetConstantBuffer( mLightBuffer, 1 );

                    Device.ImmediateContext.DrawIndexed( packet.IndexCount, 0, packet.VertexOffset );
                }
            }

            Device.ImmediateContext.PixelShader.SetShaderResource( null, 1 );
            Device.ImmediateContext.PixelShader.SetShaderResource( null, 2 );
            EndRender();
        }

        private void RunMerge()
        {
            Device.ImmediateContext.ComputeShader.Set( mMergeShader );
            Device.ImmediateContext.ComputeShader.SetUnorderedAccessView( mVPLVoxelsView, 0 );
            Device.ImmediateContext.ComputeShader.SetShaderResource( mVPLView, 0 );
            Device.ImmediateContext.ComputeShader.SetShaderResource( mVoxelsView, 1 );

            Device.ImmediateContext.Dispatch( mNumThreadGroups, 1, 1 );

            //can't have a uav and an srv that point to the same buffer bound at the same time
            Device.ImmediateContext.ComputeShader.SetUnorderedAccessView( null, 0 );
        }

        private void ShadowPass()
        {
            int width = mShadow.Texture.Description.Width;
            int height = mShadow.Texture.Description.Height;
            Color4 clear = new Color4( 1.0f, 1.0f, 1.0f, 1.0f );

            Device.ImmediateContext.OutputMerger.SetTargets( mShadow.DepthBufferView, mShadow.TextureRTV );
            Device.ImmediateContext.OutputMerger.BlendState = mNoBlend;
            Device.ImmediateContext.OutputMerger.DepthStencilState = mDepthState;

            Device.ImmediateContext.Rasterizer.State = mRasterState;
            Device.ImmediateContext.Rasterizer.SetViewports( new Viewport( 0, 0, width, height, 0.0f, 1.0f ) );
            Device.ImmediateContext.Rasterizer.SetScissorRectangles( new System.Drawing.Rectangle( 0, 0, width, height ) );

            Device.ImmediateContext.ClearRenderTargetView( mShadow.TextureRTV, clear );
            Device.ImmediateContext.ClearDepthStencilView( mShadow.DepthBufferView, DepthStencilClearFlags.Depth | DepthStencilClearFlags.Stencil, 1.0f, 0 );

            //TODO: Shadow pass
            Device.ImmediateContext.VertexShader.Set( mShadow.Shader.VertexShader );
            Device.ImmediateContext.PixelShader.Set( mShadow.Shader.PixelShader );

            Device.ImmediateContext.VertexShader.SetConstantBuffer( mShadow.CameraBuffer, 0 );
            for ( int i = 0; i < mPackets.Count; i++ )
            {
                Packet packet = mPackets[ i ];

                Device.ImmediateContext.InputAssembler.InputLayout = mShadow.Shader.Layout;
                Device.ImmediateContext.InputAssembler.PrimitiveTopology = packet.Topology;

                Device.ImmediateContext.InputAssembler.SetVertexBuffers( 0, new VertexBufferBinding( packet.VertexBuffer, packet.VertexStride, 0 ) );
                Device.ImmediateContext.InputAssembler.SetIndexBuffer( packet.IndexBuffer, Format.R32_UInt, 0 );

                Device.ImmediateContext.DrawIndexed( packet.IndexCount, 0, packet.VertexOffset );
            }

            RenderTargetView nullRTV = null;
            Device.ImmediateContext.OutputMerger.SetTargets( mShadow.DepthBufferView, nullRTV );
        }

        private void BeginRender()
        {
            Device.ImmediateContext.OutputMerger.SetTargets( mDepthBufferView, mLightView );
            Device.ImmediateContext.OutputMerger.BlendState = mNoBlend;
            Device.ImmediateContext.OutputMerger.DepthStencilState = mDepthState;

            Device.ImmediateContext.Rasterizer.State = mRasterState;
            Device.ImmediateContext.Rasterizer.SetViewports( new Viewport( 0, 0, mWidth, mHeight, 0.0f, 1.0f ) );
            Device.ImmediateContext.Rasterizer.SetScissorRectangles( new System.Drawing.Rectangle( 0, 0, mWidth, mHeight ) );

            Device.ImmediateContext.ClearRenderTargetView( mBackBufferView, ClearColor );
            Device.ImmediateContext.ClearRenderTargetView( mLightView, ClearColor );

            Device.ImmediateContext.ClearDepthStencilView( mDepthBufferView, DepthStencilClearFlags.Depth | DepthStencilClearFlags.Stencil, 1.0f, 0 );
        }

        private void EndRender()
        {
            Device.ImmediateContext.Rasterizer.State = mScreenRasterState;

            Device.ImmediateContext.OutputMerger.SetTargets( mDepthBufferView, mBackBufferView );
            Device.ImmediateContext.OutputMerger.BlendState = mNoBlend;
            Device.ImmediateContext.OutputMerger.DepthStencilState = mScreenDepthState;

            Device.ImmediateContext.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleStrip;

            Device.ImmediateContext.VertexShader.Set( mScreenShader.VertexShader );
            Device.ImmediateContext.PixelShader.Set( mScreenShader.PixelShader );

            Device.ImmediateContext.PixelShader.SetShaderResource( mLightSRV, 0 );
            Device.ImmediateContext.PixelShader.SetSampler( mSampler2D, 0 );

            Device.ImmediateContext.Draw( 3, 0 );

            Device.ImmediateContext.PixelShader.SetShaderResource( null, 0 );

            SwapChain.Present( 0, PresentFlags.None );
        }

        private void CreateD3D( IntPtr handle )
        {
            Console.WriteLine( "Creating D3D11 Device" );

            SwapChainDescription swapDesc = new SwapChainDescription()
            {
                BufferCount = 1,
                ModeDescription = new ModeDescription( mWidth, mHeight, new SlimDX.Rational( 60, 1 ), Format.R8G8B8A8_UNorm ),
                IsWindowed = true,
                OutputHandle = handle,
                SampleDescription = new SampleDescription( 1, 0 ),
                SwapEffect = SwapEffect.Discard,
                Usage = Usage.RenderTargetOutput
            };

            Device.CreateWithSwapChain( DriverType.Hardware, DeviceCreationFlags.Debug, swapDesc, out Device, out SwapChain );
            Device.Factory.SetWindowAssociation( handle, WindowAssociationFlags.IgnoreAll );

            mBackBuffer = Texture2D.FromSwapChain<Texture2D>( SwapChain, 0 );
            mBackBufferView = new RenderTargetView( Device, mBackBuffer );

            Texture2DDescription depthBufferDesc = new Texture2DDescription()
            {
                ArraySize = 1,
                BindFlags = BindFlags.DepthStencil,
                Usage = ResourceUsage.Default,
                Format = Format.D24_UNorm_S8_UInt,
                Width = mWidth,
                Height = mHeight,
                MipLevels = 1,
                SampleDescription = new SampleDescription( 1, 0 )
            };

            mDepthBuffer = new Texture2D( Device, depthBufferDesc );
            mDepthBufferView = new DepthStencilView( Device, mDepthBuffer );
        }

        private void CreateStates()
        {
            mDepthState = DepthStencilState.FromDescription( Device, new DepthStencilStateDescription()
            {
                IsDepthEnabled = true,
                IsStencilEnabled = false,
                DepthWriteMask = DepthWriteMask.All,
                DepthComparison = Comparison.Less
            } );

            mLightDepthState = DepthStencilState.FromDescription( Device, new DepthStencilStateDescription()
            {
                IsDepthEnabled = true,
                IsStencilEnabled = false,
                DepthWriteMask = DepthWriteMask.Zero,
                DepthComparison = Comparison.Equal
            } );

            mRasterState = RasterizerState.FromDescription( Device, new RasterizerStateDescription()
            {
                CullMode = CullMode.Front,
                FillMode = FillMode.Solid,
            } );

            BlendStateDescription noBlend = new BlendStateDescription();
            noBlend.IndependentBlendEnable = false;
            noBlend.AlphaToCoverageEnable = false;
            noBlend.RenderTargets[ 0 ] = new RenderTargetBlendDescription()
            {
                BlendEnable = false,
                RenderTargetWriteMask = ColorWriteMaskFlags.All
            };
            mNoBlend = BlendState.FromDescription( Device, noBlend );

            BlendStateDescription blendAdd = new BlendStateDescription();
            blendAdd.IndependentBlendEnable = false;
            blendAdd.AlphaToCoverageEnable = false;
            blendAdd.RenderTargets[ 0 ] = new RenderTargetBlendDescription()
            {
                BlendEnable = true,
                BlendOperation = BlendOperation.Add,
                SourceBlend = BlendOption.One,
                DestinationBlend = BlendOption.One,
                BlendOperationAlpha = BlendOperation.Add,
                SourceBlendAlpha = BlendOption.One,
                DestinationBlendAlpha = BlendOption.One,
                RenderTargetWriteMask = ColorWriteMaskFlags.All
            };
            mAddBlend = BlendState.FromDescription( Device, blendAdd );
        }

        private void CreateConstantBuffers()
        {
            mCameraBuffer = new Buffer( Device, new BufferDescription()
            {
                BindFlags = BindFlags.ConstantBuffer,
                CpuAccessFlags = CpuAccessFlags.Write,
                Usage = ResourceUsage.Dynamic,
                OptionFlags = ResourceOptionFlags.None,
                SizeInBytes = Marshal.SizeOf( typeof( CameraData ) ),
                StructureByteStride = 0
            } );

            mLightBuffer = new Buffer( Device, new BufferDescription()
            {
                BindFlags = BindFlags.ConstantBuffer,
                CpuAccessFlags = CpuAccessFlags.Write,
                Usage = ResourceUsage.Dynamic,
                OptionFlags = ResourceOptionFlags.None,
                SizeInBytes = AreaLight.SizeInBytes,
                StructureByteStride = 0
            } );
        }

        private void CreateScreenQuad()
        {
            mScreenShader = CreateShader( "../../Assets/Shaders/ScreenQuad.hlsl", "VSMain", "PSMain", mDefaultLayout, false );

            mScreenDepthState = DepthStencilState.FromDescription( Device, new DepthStencilStateDescription()
            {
                IsDepthEnabled = false,
                IsStencilEnabled = false,
                DepthWriteMask = DepthWriteMask.Zero,
                DepthComparison = Comparison.Never
            } );

            mScreenRasterState = RasterizerState.FromDescription( Device, new RasterizerStateDescription()
            {
                CullMode = CullMode.None,
                FillMode = FillMode.Solid,
            } );

            Texture2D texture = new Texture2D( Device, new Texture2DDescription()
            {
                ArraySize = 1,
                BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource,
                Usage = ResourceUsage.Default,
                Format = Format.R16G16B16A16_Float,
                Width = mWidth,
                Height = mHeight,
                MipLevels = 1,
                SampleDescription = new SampleDescription( 1, 0 )
            } );

            mLightView = new RenderTargetView( Device, texture );
            mLightSRV = new ShaderResourceView( Device, texture );

            mSampler2D = SamplerState.FromDescription( Device, new SamplerDescription()
            {
                Filter = Filter.MinMagMipLinear,
                AddressU = TextureAddressMode.Clamp,
                AddressV = TextureAddressMode.Clamp,
                AddressW = TextureAddressMode.Clamp,
                MipLodBias = 0.0f,
                MaximumAnisotropy = 8,
                ComparisonFunction = Comparison.Always,
                BorderColor = new Color4( 0, 0, 0 ),
                MinimumLod = 0,
                MaximumLod = float.MaxValue
            } );
        }     

        private Format GetFormatFromSemantic( string semantic )
        {
            semantic = semantic.ToUpper();
            switch ( semantic )
            {
                case "POSITION":
                    return Format.R32G32B32_Float;
                case "NORMAL":
                    return Format.R32G32B32_Float;
                case "TEXTURE":
                case "TEXCOORD":
                    return Format.R32G32_Float;
                default:
                    break;
            }

            return Format.Unknown;
        }

        private Format GetFormatFromSemantic( string semantic, CustomLayout layout )
        {
            semantic = semantic.ToUpper();
            switch ( semantic )
            {
                case "POSITION":
                    return layout.Position;
                case "NORMAL":
                    return layout.Normal;
                case "TEXTURE":
                case "TEXCOORD":
                    return layout.Texcoord;
                default:
                    break;
            }

            return Format.Unknown;
        }
    }
}
